#include "fxcc/core/graph/opengl3/phong/Effect3d.h"
#include "fxcc/core/graph/opengl3/IBuffers.h"

using namespace fxcc::graph::opengl3::phong;

using LightEffect = fxcc::graph::opengl3::phong::LightEffect;
using Material = fxcc::graph::opengl3::phong::Material;

using MeshJack = fxcc::graph::opengl3::MeshJack;
using Transform = fxcc::graph::common::Transform;

Effect3d::Effect3d()
    : fxcc::graph::opengl3::Effect3d(Effect3d::GetFragMain())
{
}



void Effect3d::Begin()
{
    fxcc::graph::opengl3::Effect3d::Begin();
};


void Effect3d::End(){
    fxcc::graph::opengl3::Effect3d::End();
}

void fxcc::graph::opengl3::phong::Effect3d::SetMaterial(const phong::Material& material)
{
    m_CurrentShader->use();
    m_CurrentShader->setVec3("material.ambient", material.ambient);
    m_CurrentShader->setVec3("material.diffuse", material.diffuse);
    m_CurrentShader->setVec3("material.specular", material.specular);
    
}
std::string Effect3d::GetFragMain()
{

    return R"(


struct  Material {
    vec3 ambient;
    float shininess;

    vec3 diffuse;
    int pad_0;

    vec3 specular;
    int pad_1;
}; 

uniform Material material;

// function prototypes
vec3 CalcDirLight(DirLight light, Material material, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light,  Material material, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light,  Material material, vec3 normal, vec3 fragPos, vec3 viewDir);

out vec4 FragColor;

vec4 Cal_Phong_Res(Material material, vec3 norm, vec3 viewDir)
{
    vec3 FragPos=vOut.FragPos;

     vec3 result=vec3(0.f,0.f,0.f);

    // dirLights
    for(int i = 0; i != lightEffect3d.numDirs; i++)
        result += CalcDirLight(lightEffect3d.dirLights[i], material,norm, viewDir);
    // pointlights
    for(int i = 0; i != lightEffect3d.numPoints; i++)
        result += CalcPointLight(lightEffect3d.pointLights[i], material, norm, FragPos, viewDir);    
    // spotlights
    for(int i = 0; i != lightEffect3d.numSpots; i++)
        result += CalcSpotLight(lightEffect3d.spotLights[i], material, norm, FragPos, viewDir);    
    
    return vec4(result, 1.0);
}

void main()
{    
    vec3 FragPos=vOut.FragPos;
    // properties
    vec3 norm = vOut.Normal;
    vec3 viewDir = normalize(passData.viewPos - FragPos);

    FragColor=Cal_Phong_Res(material,  norm, viewDir);

}

// calculates the color when using a directional light.
vec3 CalcDirLight(DirLight light, Material material, vec3 normal, vec3 viewDir)
{
    vec3 lightDir = normalize(-light.direction);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular *( spec * material.specular);
    return (ambient + diffuse + specular);
}

// calculates the color when using a point light.
vec3 CalcPointLight(PointLight light, Material material,vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular *( spec * material.specular);

    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
}

// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, Material material, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));    
    // spotlight intensity
    float theta = dot(lightDir, normalize(-light.direction)); 
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
    
    vec3 ambient = light.ambient * material.ambient.xyz;
    vec3 diffuse = light.diffuse * diff * material.diffuse.xyz;
    vec3 specular = light.specular *( spec * material.specular);

    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation *intensity;
    return (ambient + diffuse + specular);
}
)";
}
